import logging
import weakref

from google.appengine.ext import db
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore

__all__ = ['install_hooks', 'pre_save']


class _ModelSignal(object):
  """Signal which can have many listeners to many senders."""

  def __init__(self):
    self.connections = []

  def connect(self, receiver, sender=None):
    """Connect a reveiver to updates.

    Args:
      receiver: A function which receives signals.
      sender: The sender to which the receiver responds.
    """

    # TODO(joe): What if we want to connect to a bound method?
    receiver_ref = weakref.ref(receiver)
    resolve_key = (id(receiver), id(sender))

    for connection_key, ref in self.connections:
      if connection_key == resolve_key:
        break
    else:
      self.connections.append((resolve_key, receiver_ref))

  def send(self, sender, **data):
    """Notify connected receivers.

    Args:
      sender: Sender of the signal.
      data: Keyword argumnts to pass to receivers.
    """

    for (receiverid, senderid), receiver_ref in self.connections:
      if senderid == id(sender) or senderid == id(None):
        receiver = receiver_ref()
        try:
          receiver(sender=sender, **data)
        except Exception, e:
          logging.exception(e)


pre_save = _ModelSignal()


def install_hooks():
  """Installs hooks to receive updates for RPC calls to app engine"""

  def pre_save_hook(service, call, request, response):
    if call == 'Put':
      for entity_pb in request.entity_list():
        entity = datastore.Entity.FromPb(entity_pb)
        try:
          cls = db.class_for_kind(entity.kind())
        except db.KindError, e:
          continue

        model = cls.from_entity(entity)
        pre_save.send(sender=cls, model_instance=model)

  apiproxy_stub_map.apiproxy.GetPreCallHooks().Append(
    'pre_save_signals_hook', pre_save_hook, 'datastore_v3')
